﻿using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml.Linq;

namespace Dqaqs.Data
{
	[DataContract]
	public class Schema
	{
		[DataMember]
		public SchemaTable[] Tables { get; set; }

		public static Schema Parse(string schema)
		{
			if (string.IsNullOrEmpty(schema))
				return null;
			var se = XElement.Parse(schema);
			var rv = new Schema
				{
					Tables = se.Elements("Table").Select(t =>
					  new SchemaTable
						  {
							  Name = (string)t.Attribute("Name"),
							  Columns = t.Elements("Column").Select(c =>
								  new SchemaColumn
									  {
										  Name = (string)c.Attribute("Name"),
										  IsKey = (bool?)c.Attribute("IsKey") ?? false,
										  Type = (string)c.Attribute("Type")
									  }
							  ).ToArray()
						  }).ToArray()
				};

			return rv;
		}

		public override string ToString()
		{
			var rv = new XElement("Schema");
			foreach (var tbl in Tables)
			{
				var tblEl = new XElement("Table");
				tblEl.Add(new XAttribute("Name", tbl.Name));
				foreach (var column in tbl.Columns)
				{
					var colEl = new XElement("Column");
					colEl.Add(new XAttribute("Name", column.Name));
					colEl.Add(new XAttribute("IsKey", column.IsKey));
					colEl.Add(new XAttribute("Type", column.Type));
					tblEl.Add(colEl);
				}
				rv.Add(tblEl);
			}
			return rv.ToString(SaveOptions.OmitDuplicateNamespaces);
		}

		/// <summary>
		/// Compares the definition. 
		/// </summary>
		/// <returns>Returns list of table names that are different</returns>
		public static IEnumerable<Diff<string>> CompareDefinition(Schema first, Schema second)
		{
			if (first == null && second == null)
				return Enumerable.Empty<Diff<string>>();
			if (first == null)
				return second.Tables.Select(t => new Diff<string> { First = null, Second = t.Name, Type = DiffType.Added });
			if (second == null)
				return first.Tables.Select(t => new Diff<string> { First = t.Name, Second = null, Type = DiffType.Deleted });
			var modied =
				first.Tables.Join(second.Tables, f => f.Name, s => s.Name,
								(f, s) =>
									f.Columns.Any( c => !s.Columns.Any(sc => sc.Name == c.Name) ) ||
									s.Columns.Any( c => !f.Columns.Any(sc => sc.Name == c.Name))
									? new Diff<string> { First = f.Name, Second = s.Name, Type = DiffType.Modified } 
									: default(Diff<string>) )
					.Where(d => d != null)
					.Select(d => d);
			var deleted =
				first.Tables.Where(f => !second.Tables.Any(s => s.Name.Equals(f.Name)))
					.Select(f => new Diff<string> {First = f.Name, Second = null, Type = DiffType.Deleted});
			var added =
				second.Tables.Where(s => !first.Tables.Any(f => f.Name.Equals(s.Name)))
					.Select(s => new Diff<string> {First = null, Second = s.Name, Type = DiffType.Added});
			return added.Concat(deleted).Concat(modied);
		}
	}

	public class Diff<T>
	{
		public T First { get; set; }
		public T Second { get; set; }
		public DiffType Type { get; set; }
	}

	public enum DiffType { Added, Deleted, Modified }
}